iOS多线程 - GCD线程管理

GCD全称是 Grand Central Dispatch,它是基于 C 语言开发的一套多线程开发机制,也是目前苹果官方推荐的多线程开发方法。

GCD的优势

  1. GCD 是苹果公司为多核的并行运算提供的解决方案;
  2. GCD 会自动利用更多的CPU内核(比如双核、四核);
  3. GCD 会自动管理线程的生命周期(创建任务、调度任务、销毁任务);

因此,程序员只需要告诉 GCD 想要执行什么任务,把任务放在对应的 block 里面。不需要编写任何线程管理代码。

GCD在工程中的位置

GCD 存在于 libdispatch.dylib 这个库里面,这个调度库包含了 GCD 的所有的东西,任何 iOS 程序默认就加载了这个库,因此不需要我们手动导入。

同步和异步的概念

同步:就是在执行一个任务时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事

异步:异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

同步就是在同一个线程里,如果函数没有返回,线程就一直等。异步是另起一条线程来处理这个工作。

同步函数和异步函数

下面是同步函数的表达式:

1
public func dispatch_sync(queue: dispatch_queue_t, _ block: dispatch_block_t)

下面是异步函数的表达式:

1
public func dispatch_async(queue: dispatch_queue_t, _ block: dispatch_block_t)

下面是两个函数的传入参数:

参数 作用 传入值
queue: dispatch_queue_t 这是一个队列 队列
_ block: dispatch_block_t 这是我们要执行的一个任务 block

同步函数不会创建新的线程,当我们执行 dispatch_sync这个函数时 它会一直等待 block 的完成,block 执行完这个函数才返回。

当执行 dispatch_async 时,不论 block 是否执行完,只要调用了函数就返回了。

下面讲几个例子来帮助我们理解同步和异步:

同步:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式。
异步:发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。

同步:提交请求->等待服务器处理->处理完毕返回 这个期间客户端不能干任何事
异步:请求通过事件触发->服务器处理(这时客户端仍然可以做其他事情)->处理完毕

同步:就是你叫我去吃饭,我听到了就和你去吃饭;如果没有听到,你就不停的叫,直到我告诉你我听到了,才一起去吃饭。
异步就是你叫我,然后自己去吃饭,我得到消息后可能立即走,也可能等到下班才去吃饭。

所以,要我请你吃饭就用同步的方法,要请我吃饭就用异步的方法。

同步函数和异步函数的使用场景

1
2
3
4
5
6
7
8
9
10
dispatch_async(concutentQueue, ^{
//长时间处理
//例如AR用动画识别
//例如数据库访问
//长时间处理结束,主线程使用该处理结果
dispatch_async(mainQueue, ^{
//只在主线程可以执行的处理
//例如用户界面更新
});
});

CGD的 3 种队列

使用 GCD 开发基本看不到线程,线程的细节都被封装起来了,我们直接把要执行的任务添加到队列里就可以了,GCD 会根据指定的队列 ,将队列里的任务取出来放在对应的线程执行。任务的取出遵循队列的 FIFO 原则:先进先出,后进后出。

队列分为两种,一种是串行队列,一种是并行队列。但是具体到GCD里面,队列就分为了 3 种。

  1. 主队列

    主队列是指运行在主线程里的 Main Queue , 主队列不需要创建,可以通过 dispatch_get_main_queue 获取到主队列。

  2. 并行队列

    并行队列是一种全局并行队列,同样不需要自己创建,通过 dispatch_get_global_queue 可以获取到全局的并行队列,而且这个全局的并行队列有3个,因为有 3个不同的优先级 。我们可以通过使用 dispatch_get_main_queue 获取全局并行队列,在获取时可以通过指定它的优先级,来获取不同优先级的并行队列。并行队列的执行顺序和加入队列的顺序相同,也就是先入先出的原则。

  3. 自定义队列

    自定义队列就是我们自己创建的队列,我们可以通过 dispatch_queue_creat 进行创建,创建自定义队列时可以声明其是串行队列还是并行队列。

同步、异步、并行、串行的概念比较

  1. 同步和异步决定了要不要开启新的线程:

    同步:在当前进行中执行任务,不具备开启新线程的能力;

    异步:在新的线程中执行任务,具备开启新线程的能力;

  2. 并行和串行决定了任务执行的方式

    并行:多个任务并发(同时)执行;

    串行:一个任务执行完毕后,再执行下一个任务;

使用异步函数执行主队列

1
2
3
4
let mainQueue: dispatch_queue_t = dispatch_get_main_queue() //获取主队列
dispatch_async(mainQueue) {
print("使用异步函数执行主队列,所在的线程是\(NSThread.currentThread())")
}

通过打印日志,我们可以知道主队列是在主线程中执行。

使用异步函数执行并行队列

获取并行队列

1
public func dispatch_get_global_queue(identifier: Int, _ flags: UInt) -> dispatch_queue_t!

传入参数如下:

形参 作用 传入值
identifier: Int 表示队列的优先级。 见下一个表格
_ flags: UInt 为以后使用的标记,传入0以外的值可能会返回空值。 0

identifier: IntiOS7 以后可以使用的值:

值的名称 含义:使用场景
QOS_CLASS_USER_INTERACTIVE 用户交互:尽快完成,用户在期待结果,不要放太耗时操作
QOS_CLASS_USER_INITIATED 用户期望:不要放太耗时操作。
QOS_CLASS_DEFAULT 默认:不是给程序员使用的,用来重置对列使用的。
QOS_CLASS_UTILITY 实用工具:耗时操作,可以使用这个选项。
QOS_CLASS_BACKGROUND 后台

identifier: IntiOS7 以前使用的值:

值的名称 对应关系 优先级
DISPATCH_QUEUE_PRIORITY_HIGH QOS_CLASS_USER_INITIATED 高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT QOS_CLASS_DEFAULT 默认优先级
DISPATCH_QUEUE_PRIORITY_LOW QOS_CLASS_UTILITY 低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND QOS_CLASS_BACKGROUND 后台优先级

值得注意的是:BACKGROUND 表示用户不需要知道任务什么时候完成,如果选择这个选项速度慢得令人发指,非常不利于调试!对于优先级推荐不要搞得太负责,就用最简单,以免发生优先级反转。

下面我们创建 2 个异步函数并执行一个并行队列:

1
2
3
4
5
6
7
8
let concurentQueue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)
dispatch_async(concurentQueue) {
print("使用异步函数执行第1个全局并行队列,线程是\(NSThread.currentThread())")
}
dispatch_async(concurentQueue) {
print("使用异步函数执行第2个全局并行队列,线程是\(NSThread.currentThread())")
}

通过打印日志我们可以知道:如果使用全局并行队列,并且使用异步函数执行时,异步函数会创建一个新的并行线程,并行线程和主线程是同时运行的,所以哪个先执行并不能掌握。

每当有一个异步函数执行并行队列,都会创建一个新的子线程,并在这个新的线程中运行。但这并不意味着你使用很多异步函数执行并行队列,就会有很多个子线程,当这种情况发生时,系统会将你在 block 的操作封装给其他的子线程运行。

使用异步函数执行自定义队列

创建自定义队列

1
public func dispatch_queue_create(label: UnsafePointer<Int8>, _ attr: dispatch_queue_attr_t!) -> dispatch_queue_t!

传入参数如下:

形参 作用 传入值
label: UnsafePointer 队列的名称 字符串,一般是请求的操作,可以是nil。
_ attr: dispatch_queue_attr_t! 队列的类型 串行队列:DISPATCH_QUEUE_SERIAL
并行队列:DISPATCH_QUEUE_CONCURRENT

下面我们创建 2 个异步函数并执行一个串行行队列:

1
2
3
4
5
6
7
let myQueue = dispatch_queue_create("请求的操作", DISPATCH_QUEUE_SERIAL)
dispatch_async(myQueue) {
print("使用异步函数执行第1个串行队列,线程是\(NSThread.currentThread())")
}
dispatch_async(myQueue) {
print("使用异步函数执行第2个串行队列,线程是\(NSThread.currentThread())")
}

通过打印结果,我们可以知道:如果使用串行队列,并且是异步函数时,不论有多少异步函数,只会创建一个新的线程。

异步函数执行不同队列的对比

异步函数
获取的主队列 没有开启新线程,串行执行任务。
获取的并行队列 有开启新线程且可多开线程,并行执行任务。
创建的并行队列 有开启新线程但只可开一条,串行执行任务。